package org.lightdi.container.factory;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import org.lightdi.container.DIContainer;
import org.lightdi.container.config.ComponentConfig;
import org.lightdi.container.config.ContainerConfig;
import org.lightdi.container.config.ComponentConfig.ConstructorArg;
import org.lightdi.container.config.ComponentConfig.SetterArg;
import org.lightdi.container.impl.LightDIContainerImpl;
import org.lightdi.container.meta.MetaComponent;
import org.lightdi.container.meta.MetaComponentRef;
import org.lightdi.container.parser.ConfigLoadUtil;
import org.lightdi.container.tag.LightDIConfigTag;
import org.lightdi.container.util.LightDILoggerUtil;
import org.lightdi.util.ArrayUtil;
import org.lightdi.util.LoggerUtil;
import org.lightdi.util.ResourceUtil;

public class LightDIContainerFactory {

	static final LightDIContainerManager containerManager = new LightDIContainerManager();

	private static final String LIGHT_DI_CONFIG_PATH = "lightdi-config.xml";

	private static final Logger logger = Logger
			.getLogger(DIContainer.DEFAULT_CONTAINER_NAME);

	private LightDIContainerFactory() {
	}

	private static final String MSG_PREFIX = "LIGHTDI : ";

	static {
		try {
			initialize();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void initialize() throws IllegalArgumentException {
		// create di containers
		InputStream is = ResourceUtil.getResourceStream(LIGHT_DI_CONFIG_PATH);
		try {
			// load start
			if (is != null) {

				String startMsg = MSG_PREFIX + "Loading containers...";
				LoggerUtil.info(logger, startMsg);

				// add default config.xml
				addConfigXML(LIGHT_DI_CONFIG_PATH);

				// config load
				parseAllConfigXMLs2ContainerManager();
				// create containers
				loadContainersFromConfigs2ContainerManager();

				// output loaded containers
				Map<String, DIContainer> containers = getLoadedContainers();
				Set<String> nameSet = containers.keySet();
				for (String name : nameSet)
					LightDILoggerUtil.info(
							LightDIConfigTag.Attributes.LoadingLog.DEFAULT,
							logger, null, null, "----- " + name + " Loaded");
				String endMsg = MSG_PREFIX + "Loading end. ("
						+ containers.size() + " containers)";
				LoggerUtil.info(logger, endMsg);
			}

		} catch (Exception e) {
			logger.warning("Clear all the containers before initializing.");
			throw new IllegalStateException("--- LightDI :: StaticLoad"
					+ LightDILoggerUtil.ERROR);
		} finally {
			ResourceUtil.close(is);
		}

	}

	public static void clearAllContainers() {

		String startMsg = MSG_PREFIX + "Clearing all containers start...";
		LoggerUtil.info(logger, startMsg);

		// clear registered container configs
		Map<String, ContainerConfig> newConfigs = new ConcurrentHashMap<String, ContainerConfig>();
		containerManager.setRegisteredContainerConfigs(newConfigs);
		// set loaded flag false in configXMLs
		Map<String, ContainerConfig> configXMLs = containerManager
				.getRegisteredConfigXMLs();
		Set<String> xmlNames = containerManager.getRegisteredConfigXMLs()
				.keySet();
		for (String xmlName : xmlNames) {
			ContainerConfig target = configXMLs.get(xmlName);
			target.setLoaded(false);
			containerManager.getRegisteredConfigXMLs().put(xmlName, target);
		}
		// clear loaded DIContainers
		Map<String, DIContainer> newContainers = new ConcurrentHashMap<String, DIContainer>();
		containerManager.setLoadedContainers(newContainers);

		String endMsg = MSG_PREFIX + "Clearing all containers end.";
		LoggerUtil.info(logger, endMsg);

	}

	public static void reloadContainers() throws Exception {

		String startMsg = MSG_PREFIX + "Re-Loading the containers start...";
		LoggerUtil.info(logger, startMsg);

		parseAllConfigXMLs2ContainerManager();
		loadContainersFromConfigs2ContainerManager();

		String endMsg = MSG_PREFIX + "Re-Loading containers end.";
		LoggerUtil.info(logger, endMsg);

	}

	private static void parseAllConfigXMLs2ContainerManager() throws Exception {

		Map<String, ContainerConfig> registeredConfigs = containerManager
				.getRegisteredContainerConfigs();

		Map<String, ContainerConfig> registeredConfigXMLs = containerManager
				.getRegisteredConfigXMLs();

		Set<String> xmlNames = registeredConfigXMLs.keySet();
		for (String xmlName : xmlNames) {
			// skip if already loaded config.xml
			if (registeredConfigXMLs.get(xmlName).isLoaded())
				continue;

			// read config xml and register it
			List<Map<String, ContainerConfig>> ret = ConfigLoadUtil
					.readConfigXML(xmlName, registeredConfigs,
							registeredConfigXMLs);
			registeredConfigs = ret.get(0);
			registeredConfigXMLs = ret.get(1);
		}

		// save loaded configs
		containerManager.setRegisteredContainerConfigs(registeredConfigs);
		containerManager.setRegisteredConfigXMLs(registeredConfigXMLs);

	}

	private static void loadContainersFromConfigs2ContainerManager()
			throws Exception {

		// re-create all di containers
		Map<String, DIContainer> loadedContainers = new ConcurrentHashMap<String, DIContainer>();

		// container configs
		Map<String, ContainerConfig> registeredContainerConfigs = null;
		registeredContainerConfigs = containerManager
				.getRegisteredContainerConfigs();

		// re-create each di container
		Set<String> containerNames = registeredContainerConfigs.keySet();
		for (String containerName : containerNames) {
			ContainerConfig containerConfig = registeredContainerConfigs
					.get(containerName);
			DIContainer container = createContainerFromConfig(containerConfig);
			container = createSingletonComponents(container);
			loadedContainers.put(container.getName(), container);
		}
		// save new containers
		containerManager.setLoadedContainers(loadedContainers);

	}

	private static DIContainer createSingletonComponents(DIContainer container) {

		List<MetaComponent> metaComponents = container.getMetaComponents();

		for (MetaComponent metaComponent : metaComponents) {
			if (metaComponent.getInstanceType().equals(
					MetaComponent.InstanceType.SINGLETON)) {
				// get constructor args
				for (Object constArg : metaComponent.getConstructorArgValues()) {
					if (constArg instanceof MetaComponentRef) {
						MetaComponentRef ref = (MetaComponentRef) constArg;
						constArg = container.getComponent(ref.getName());
					}
				}
				// get setter args
				for (Object setArg : metaComponent.getSetterArgValues()) {
					if (setArg instanceof MetaComponentRef) {
						MetaComponentRef ref = (MetaComponentRef) setArg;
						setArg = container.getComponent(ref.getName());
					}
				}
				// register or update
				String componentName = metaComponent.getName();
				Object component = container.getComponent(metaComponent
						.getName());
				container.getSingletonComponents()
						.put(componentName, component);
			}
		}
		return container;
	}

	private static DIContainer createContainerFromConfig(
			ContainerConfig containerConfig) throws ClassNotFoundException,
			NoSuchMethodException {

		// ********** create DIContainer **********
		DIContainer dest = new LightDIContainerImpl();
		dest.setName(containerConfig.getName());
		List<MetaComponent> metaComponents = new ArrayList<MetaComponent>();
		dest.setMetaComponents(metaComponents);
		Map<String, Object> singletonComponents = new ConcurrentHashMap<String, Object>();
		dest.setSingletonComponents(singletonComponents);

		// ********** create each component in the container **********
		Map<String, ComponentConfig> componentConfigs = containerConfig
				.getComponents();
		Set<String> componentNames = componentConfigs.keySet();
		for (String componentName : componentNames) {
			// copy to MetaComponent
			ComponentConfig componentConfig = componentConfigs
					.get(componentName);
			MetaComponent metaComponent = new MetaComponent();
			metaComponent.setName(componentConfig.getName());
			metaComponent.setClassName(componentConfig.getClassName());
			metaComponent.setValue(componentConfig.getValue());
			metaComponent.setInstanceType(componentConfig.getInstanceType());
			metaComponent.setInitMethods(componentConfig.getInitMethods());
			Class<?> componentClazz = Class.forName(metaComponent
					.getClassName());

			// ********** create constructor **********
			// constructor arg setting
			List<ConstructorArg> constructorArgs = componentConfig
					.getConstructorArgs();
			for (ConstructorArg arg : constructorArgs) {
				if (arg == null)
					break;

				Class<?> conClass = null;
				Object conValue = null;

				// component ref
				if (arg.getRef() != null) {
					// prototype only
					ComponentConfig referred = componentConfigs.get(arg
							.getRef().getName());
					String instanceType = referred.getInstanceType();
					if (MetaComponent.InstanceType.SINGLETON
							.equals(instanceType)) {
						throw new IllegalStateException(
								"Instance type of the referred component must be 'prototype' : "
										+ arg.getRef().getName());
					}
					conClass = MetaComponentRef.class;
					conValue = arg.getRef();
				} else {
					String conClassName = arg.getClassName();
					try {
						conClass = Class.forName(conClassName);
					} catch (ClassNotFoundException e) {
						String containerName = containerConfig.getName();
						String msg = "Class Not Found!"
								+ LightDILoggerUtil.ERROR;
						LightDILoggerUtil.fatal(
								LightDIConfigTag.Attributes.LoadingLog.DEFAULT,
								logger, containerName, componentName, msg);
						throw e;
					}
					conValue = arg.getValue();
				}
				metaComponent.getConstructorArgTypes().add(conClass);
				metaComponent.getConstructorArgValues().add(conValue);
			}
			// constructor arg ref setting
			List<Class<?>> conTypes = metaComponent.getConstructorArgTypes();
			List<Object> conValues = metaComponent.getConstructorArgValues();
			int conLen = conTypes.size();
			for (int i = 0; i < conLen; i++) {
				if (conTypes.get(i) == MetaComponentRef.class) {
					MetaComponentRef ref = (MetaComponentRef) conValues.get(i);
					ComponentConfig refConfig = (ComponentConfig) componentConfigs
							.get(ref.getName());
					conTypes.set(i, Class.forName(refConfig.getClassName()));
				}
			}
			// set constructor
			Constructor<?> constructor = null;
			try {
				constructor = componentClazz.getConstructor(ArrayUtil
						.toArray(conTypes));
			} catch (NoSuchMethodException e) {
				String containerName = containerConfig.getName();
				String msg = "Constructor Not Found!" + LightDILoggerUtil.ERROR;
				LightDILoggerUtil.fatal(
						LightDIConfigTag.Attributes.LoadingLog.DEFAULT, logger,
						containerName, componentName, msg);
				throw e;
			}
			metaComponent.setConstructor(constructor);

			// ********** create setter config **********
			// setter arg setting
			List<SetterArg> setterArgs = componentConfig.getSetterArgs();
			for (SetterArg setterArg : setterArgs) {
				if (setterArg == null)
					continue;
				metaComponent.getSetterNames().add(setterArg.getName());
				Class<?> setClass = null;
				Object setValue = null;
				// component ref
				if (setterArg.getRef() != null) {
					// prototype only
					String referredName = setterArg.getRef().getName();
					ComponentConfig referred = componentConfigs
							.get(referredName);
					String setterInstanceType = referred.getInstanceType();
					if (MetaComponent.InstanceType.SINGLETON
							.equals(setterInstanceType)) {
						throw new IllegalStateException(
								"Instance type of the referred component must be 'prototype' : "
										+ referredName);
					}
					setClass = MetaComponentRef.class;
					setValue = setterArg.getRef();
				} else {
					setClass = Class.forName(setterArg.getClassName());
					setValue = setterArg.getValue();
				}
				// save setter args
				metaComponent.getSetterArgTypes().add(setClass);
				metaComponent.getSetterArgValues().add(setValue);
			}
			// setter arg ref setting
			List<Class<?>> setTypes = metaComponent.getSetterArgTypes();
			List<Object> setValues = metaComponent.getSetterArgValues();
			int setLen = setTypes.size();
			for (int i = 0; i < setLen; i++) {
				if (setTypes.get(i) == MetaComponentRef.class) {
					MetaComponentRef ref = (MetaComponentRef) setValues.get(i);
					ComponentConfig refConfig = (ComponentConfig) componentConfigs
							.get(ref.getName());
					setTypes.set(i, Class.forName(refConfig.getClassName()));
				}
			}
			metaComponents.add(metaComponent);
		}
		return dest;
	}

	public static DIContainer getDefaultContainer() {

		return containerManager.getLoadedContainers().get(
				DIContainer.DEFAULT_CONTAINER_NAME);

	}

	public static DIContainer getContainer(String containerName) {

		return containerManager.getLoadedContainers().get(containerName);

	}

	public static Map<String, DIContainer> getLoadedContainers() {

		return containerManager.getLoadedContainers();

	}

	public static void addConfigXML(String path) {

		containerManager.getRegisteredConfigXMLs().put(path,
				new ContainerConfig());

	}

	public static void removeCofingXML(String path) {

		containerManager.getRegisteredConfigXMLs().remove(path);

	}
}
